package cn.trigram.lang;

import ch.obermuhlner.math.big.DefaultBigDecimalMath;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * BigDecimal 数学函数运算工具类。
 * <br/>
 * 使用big-math库对BigDecimal实现常见的运算封装，包括求余、开方、平方、对数、幂数、各种三角函数；
 * 常量PI和常量E。
 * <br/>
 * <b>注：采用默认的38位精度，32位小数，四舍五入模式</b>
 *
 * @version 1.0.0
 * @date 2022/08/02 09:12:38
 * @since 1.0.0
 */
public class BigMathUtil {
    /**
     * 默认精度，38，取oracle的number的precision最大值
     */
    public static final int DEFAULT_PRECISION = 38;
    /**
     * 默认小数位，32
     */
    public static final int DEFAULT_SCALE = 32;
    /**
     * 默认四舍五入
     */
    public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP;

    public static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(DEFAULT_PRECISION, DEFAULT_ROUNDING_MODE);

    /**
     * -1
     */
    public static final BigDecimal NEGATIVE_ONE = withMathContext(() -> new BigDecimal("-1", DefaultBigDecimalMath.currentMathContext()));

    /**
     * 100
     */
    public static final BigDecimal HUNDRED = withMathContext(() -> new BigDecimal("100", DefaultBigDecimalMath.currentMathContext()));

    /**
     * 自然常量π=3.1415926535897932384626433832795
     */
    public static final BigDecimal PI = withMathContext(DefaultBigDecimalMath::pi);

    /**
     * 自然常量e=2.7182818284590452353602874713527
     */
    public static final BigDecimal E = withMathContext(DefaultBigDecimalMath::e);

    public static BigDecimal abs(BigDecimal x) {
        return withMathContext(() -> lt(x, BigDecimal.ZERO) ? multiply(x, NEGATIVE_ONE) : x);
    }

    /**
     * 加法运算，null值默认为0
     *
     * @param x
     * @param y
     * @return {@link BigDecimal }
     */
    public static BigDecimal add(BigDecimal x, BigDecimal y) {
        return withMathContext(() -> DefaultBigDecimalMath.add(nullToZero(x), nullToZero(y)));
    }

    /**
     * 减法运算，null值默认为0
     *
     * @param x
     * @param y
     * @return {@link BigDecimal }
     */
    public static BigDecimal subtract(BigDecimal x, BigDecimal y) {
        return withMathContext(() -> DefaultBigDecimalMath.subtract(nullToZero(x), nullToZero(y)));
    }

    /**
     * 乘法运算，null值默认为0
     *
     * @param x
     * @param y
     * @return {@link BigDecimal }
     */
    public static BigDecimal multiply(BigDecimal x, BigDecimal y) {
        return withMathContext(() -> DefaultBigDecimalMath.multiply(nullToZero(x), nullToZero(y)));
    }

    /**
     * 除法运算，被除数null默认为0，除数为null或0作Optional处理
     *
     * @param x
     * @param y
     * @return {@link Optional<BigDecimal> }
     */
    public static Optional<BigDecimal> divide(BigDecimal x, BigDecimal y) {
        x = nullToZero(x);
        y = nullToZero(y);
        if (eq(y, BigDecimal.ZERO)) {
            return Optional.empty();
        }
        final BigDecimal finalX = x;
        final BigDecimal finalY = y;
        return Optional.ofNullable(withMathContext(() -> DefaultBigDecimalMath.divide(finalX, finalY)));
    }

    /**
     * 乘方运算 x^y
     *
     * @param x
     * @param y 幂
     * @return {@link BigDecimal }
     */
    public static BigDecimal pow(BigDecimal x, BigDecimal y) {
        return withMathContext(() -> DefaultBigDecimalMath.pow(x, y));
    }

    /**
     * 乘方运算 x^y
     *
     * @param x
     * @param y 幂
     * @return {@link BigDecimal }
     */
    public static BigDecimal pow(BigDecimal x, long y) {
        return withMathContext(() -> DefaultBigDecimalMath.pow(x, y));
    }

    /**
     * 快捷求平方
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal pow2(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.pow(x, 2));
    }

    /**
     * 快捷开平方 x^0.5
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal sqrt(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.sqrt(x));
    }

    /**
     * 开方运算
     *
     * @param x 不能为负数
     * @param n 次方值
     * @return {@link BigDecimal } 负数返回
     */
    public static BigDecimal root(BigDecimal x, BigDecimal n) {
        return withMathContext(() -> DefaultBigDecimalMath.root(x, n));
    }

    /**
     * 开方运算
     *
     * @param x
     * @param n 次方值
     * @return {@link BigDecimal }
     */
    public static BigDecimal root(BigDecimal x, long n) {
        return withMathContext(() -> DefaultBigDecimalMath.root(x, new BigDecimal(n)));
    }

    /**
     * 利用对数的恒等式  log x(y)= ln(y)/ln(x)
     *
     * @param x 对数底
     * @param y 值
     * @return 对数运算
     */
    public static BigDecimal log(BigDecimal x, BigDecimal y) {

        return withMathContext(() -> ln(y).divide(ln(x), DEFAULT_MATH_CONTEXT));
    }

    /**
     * 以e为底的log函数求值
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal ln(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.log(x));
    }

    /**
     * 以2为底的log函数求值
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal log2(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.log2(x));
    }

    /**
     * 以10为底的log函数求值
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal lg(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.log10(x));
    }

    /**
     * e的x次方的幂函数求值e^x
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal exp(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.exp(x));
    }

    /**
     * 正弦函数求值sin(x)
     *
     * @param x 角度值 [-π,π]
     * @return {@link BigDecimal }
     */
    public static BigDecimal sin(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.sin(x));
    }

    /**
     * 余弦函数求值cos(x)
     *
     * @param x 角度值 [-π,π]
     * @return {@link BigDecimal }
     */
    public static BigDecimal cos(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.cos(x));
    }

    /**
     * 正切函数求值tan(x)
     *
     * @param x 角度值 [-π,π]
     * @return {@link BigDecimal }
     */
    public static BigDecimal tan(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.tan(x));
    }

    /**
     * 余切函数求值cot(x)
     *
     * @param x 角度值 [-π,π]
     * @return {@link BigDecimal }
     */
    public static BigDecimal cot(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.cot(x));
    }

    /**
     * 求倒数 1/x
     *
     * @param x
     * @return {@link BigDecimal }
     */
    public static BigDecimal reciprocal(BigDecimal x) {
        return withMathContext(() -> DefaultBigDecimalMath.reciprocal(x));
    }

    /**
     * 求余数 等于 % 运算；等价 x%y
     * <br/>
     * 被除数null默认为0，除数为null或0作Optional处理
     *
     * @param x
     * @param y
     * @return {@link BigDecimal }
     */
    public static Optional<BigDecimal> remainder(BigDecimal x, BigDecimal y) {
        x = nullToZero(x);
        y = nullToZero(y);
        if (eq(y, BigDecimal.ZERO)) {
            return Optional.empty();
        }
        final BigDecimal finalX = x;
        final BigDecimal finalY = y;
        return Optional.ofNullable(withMathContext(() -> DefaultBigDecimalMath.remainder(finalX, finalY)));
    }

    /**
     * 比较运算：x!=y
     *
     * @param x
     * @param y
     * @return boolean
     */
    public static boolean neq(BigDecimal x, BigDecimal y) {
        return x.compareTo(y) != 0;
    }

    /**
     * 比较运算：x==y
     *
     * @param x
     * @param y
     * @return boolean
     */
    public static boolean eq(BigDecimal x, BigDecimal y) {
        return x.compareTo(y) == 0;
    }

    /**
     * 比较运算：x&lt;y
     *
     * @param x
     * @param y
     * @return boolean
     */
    public static boolean lt(BigDecimal x, BigDecimal y) {
        return x.compareTo(y) < 0;
    }

    /**
     * 比较运算：x<=y
     *
     * @param x
     * @param y
     * @return boolean
     */
    public static boolean lte(BigDecimal x, BigDecimal y) {
        return x.compareTo(y) <= 0;
    }

    /**
     * 比较运算：x>y
     *
     * @param x
     * @param y
     * @return boolean
     */
    public static boolean gt(BigDecimal x, BigDecimal y) {
        return x.compareTo(y) > 0;
    }

    /**
     * 比较运算：x>=y
     *
     * @param x
     * @param y
     * @return boolean
     */
    public static boolean gte(BigDecimal x, BigDecimal y) {
        return x.compareTo(y) >= 0;
    }

    public static BigDecimal unityMathContext(BigDecimal x) {
        if (Objects.isNull(x)) {
            return x;
        }
        return BigDecimal.ZERO.add(x, DEFAULT_MATH_CONTEXT);
    }

    private static BigDecimal nullToZero(BigDecimal x) {
        return Objects.isNull(x) ? BigDecimal.ZERO : x;
    }

    private static BigDecimal withMathContext(Supplier<BigDecimal> supplier) {
        try (
                final DefaultBigDecimalMath.LocalMathContext context = DefaultBigDecimalMath.createLocalMathContext(DEFAULT_PRECISION, DEFAULT_ROUNDING_MODE)
        ) {
            return supplier.get();
        }
    }
}
